#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <cstring>
#include <iostream>
#include <linux/dma-buf.h>
#include "libdmabufheap/dmabuf_heap.h"
#include "dal_dmabufheap/DmaBufHeapManager.hpp"

namespace dal_dmabufheap {

DmaBufHeapManager* DmaBufHeapManager::instance_ = nullptr;

DmaBufHeapManager::DmaBufHeapManager()
{
    heap_fd_ = -1;
    std::cout << device_path_ << std::endl;
    if (0 != dmabuf_heap_open(const_cast<char*>(device_path_.c_str()), &heap_fd_)) {
        std::cout << "[DmaBufHeapManager::DmaBufHeapManager] dmabuf_heap_open failed, please inmod "
                     "ipc device."
                  << std::endl;
        is_work_.store(false);
        return;
    }
    is_work_.store(true);
}

DmaBufHeapManager::~DmaBufHeapManager()
{
    if (is_work_.load()) {
        close(heap_fd_);
    }
}

DmaBufHeapManager* DmaBufHeapManager::GetInstance()
{
    if (nullptr == instance_) {
        instance_ = new DmaBufHeapManager();
    }
    return instance_;
}

bool DmaBufHeapManager::CreateBlock(uint32_t size, Block* block)
{
    if (!is_work_.load()) {
        return false;
    }

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::CreateBlock] block is nullptr." << std::endl;
        return false;
    }

    if (0 != dmabuf_heap_alloc(heap_fd_, size, DMA_HEAP_VALID_HEAP_FLAGS, &block->dmabuf_fd)) {
        std::cout << "[DmaBufHeapManager::CreateBlock] dmabuf_heap_alloc failed." << std::endl;
        return false;
    }
    auto p =
        mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, block->dmabuf_fd, 0);
    if (p == MAP_FAILED) {
        std::cout << "[DmaBufHeapManager::CreateBlock] mmap failed." << std::endl;
        return false;
    }
    block->ptr = p;
    block->size = size;

    memset(p, 0, size);

    if (0 != dmabuf_heap_get_global_fd(heap_fd_, block->dmabuf_fd, &block->global_fd)) {
        std::cout << "[DmaBufHeapManager::CreateBlock] dmabuf_heap_get_global_fd failed."
                  << std::endl;
        return false;
    }

    // std::cout << "[DmaBufHeapManager::CreateBlock] succeed, "
    //           << "  - dmabuf_fd: " << block->dmabuf_fd
    //           << "  - global_fd: " << block->global_fd
    //           << "  - size: " << block->size
    //           << "  - ptr: " << block->ptr
    //           << std::endl;

    return true;
}

bool DmaBufHeapManager::GetMappedBlock(Block* block)
{
    // std::cout << "[DmaBufHeapManager::GetMappedBlock] start" << std::endl;

    if (!is_work_.load()) {
        return false;
    }

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::GetMappedBlock] block is nullptr." << std::endl;
        return false;
    }

    if (block->global_fd < 0) {
        std::cout << "[DmaBufHeapManager::GetMappedBlock] global_fd < 0." << std::endl;
        return false;
    }

    if (0 != dmabuf_heap_import_global_fd(
                 heap_fd_, block->global_fd, O_CLOEXEC | O_RDONLY, &block->dmabuf_fd)) {
        std::cout << "[DmaBufHeapManager::GetMappedBlock] dmabuf_heap_import_global_fd failed."
                  << std::endl;
        return false;
    }

    auto p = mmap(NULL, block->size, PROT_READ, MAP_SHARED | MAP_POPULATE, block->dmabuf_fd, 0);
    if (p == MAP_FAILED) {
        std::cout << "[DmaBufHeapManager::GetMappedBlock] mmap failed." << std::endl;
        return false;
    }

    block->ptr = p;

    // std::cout << "[DmaBufHeapManager::GetMappedBlock] succeed, "
    //     << "  - dmabuf_fd: " << block->dmabuf_fd
    //     << "  - global_fd: " << block->global_fd
    //     << "  - size: " << block->size
    //     << "  - ptr: " << block->ptr
    //     << std::endl;
    return true;
}

bool DmaBufHeapManager::DestroyBlock(Block* block)
{
    if (!is_work_.load()) {
        return false;
    }

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::DestroyBlock] block is nullptr." << std::endl;
        return false;
    }

    if (block->ptr != MAP_FAILED) {
        munmap(block->ptr, block->size);
    }

    if (block->dmabuf_fd >= 0) {
        close(block->dmabuf_fd);
    }

    block->ptr = MAP_FAILED;
    block->dmabuf_fd = -1;
    block->global_fd = -1;

    // std::cout << "[DmaBufHeapManager::DestroyBlock] succeed, "
    //           << "  - dmabuf_fd: " << block->dmabuf_fd
    //           << "  - global_fd: " << block->global_fd
    //           << "  - size: " << block->size
    //           << "  - ptr: " << block->ptr
    //           << std::endl;
    return true;
}

bool DmaBufHeapManager::ResizeBlock(uint32_t size, Block* block)
{
    return DestroyBlock(block) && CreateBlock(size, block);
}

bool DmaBufHeapManager::WriteAndSyncBlock(Block* block, uint32_t pos, void* data, uint32_t size)
{
    if (!is_work_.load()) {
        return false;
    }

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::WriteAndSyncBlock] block is nullptr." << std::endl;
        return false;
    }

    if (block->dmabuf_fd < 0) {
        std::cout << "[DmaBufHeapManager::WriteAndSyncBlock] dmabuf_fd < 0." << std::endl;
        return false;
    }

    if (pos + size > block->size) {
        std::cout << "[DmaBufHeapManager::WriteAndSyncBlock] pos + size > block->size."
                  << std::endl;
        return false;
    }

    if (block->ptr == MAP_FAILED) {
        std::cout << "[DmaBufHeapManager::WriteAndSyncBlock] block->ptr == MAP_FAILED."
                  << std::endl;
        return false;
    }

    memcpy(reinterpret_cast<char*>(block->ptr) + pos, data, size);
    dmabuf_heap_sync_cache_clean(reinterpret_cast<char*>(block->ptr),
                                 reinterpret_cast<char*>(block->ptr) + block->size);

    return true;
}

bool DmaBufHeapManager::SyncAndReadBlock(Block* block, uint32_t pos, void* data, uint32_t size)
{
    if (!is_work_.load()) {
        return false;
    }

    // std::cout << "[DmaBufHeapManager::SyncAndReadBlock] start, "
    //     << "  - dmabuf_fd: " << block->dmabuf_fd
    //     << "  - global_fd: " << block->global_fd
    //     << "  - size: " << block->size
    //     << "  - ptr: " << block->ptr
    //     << "  - data : " << data
    //     << "  - pos : " << pos
    //     << "  - size : " << size
    //     << std::endl;

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::SyncAndReadBlock] block is nullptr." << std::endl;
        return false;
    }

    if (block->dmabuf_fd < 0) {
        std::cout << "[DmaBufHeapManager::SyncAndReadBlock] dmabuf_fd < 0." << std::endl;
        return false;
    }

    if (pos + size > block->size) {
        std::cout << "[DmaBufHeapManager::SyncAndReadBlock] pos + size > block->size." << std::endl;
        return false;
    }

    if (block->ptr == MAP_FAILED) {
        std::cout << "[DmaBufHeapManager::SyncAndReadBlock] block->ptr == MAP_FAILED." << std::endl;
        return false;
    }

    dmabuf_heap_sync_cache_inval(reinterpret_cast<char*>(block->ptr),
                                 reinterpret_cast<char*>(block->ptr) + block->size);
    memcpy(data, reinterpret_cast<char*>(block->ptr) + pos, size);

    // std::cout << "[DmaBufHeapManager::SyncAndReadBlock] end, "
    // << "  - dmabuf_fd: " << block->dmabuf_fd
    // << "  - global_fd: " << block->global_fd
    // << "  - size: " << block->size
    // << "  - ptr: " << block->ptr
    // << "  - data : " << data
    // << "  - pos : " << pos
    // << "  - size : " << size
    // << std::endl;

    return true;
}

bool DmaBufHeapManager::SyncBlockReadMode(Block* block)
{
    // std::cout << "[DmaBufHeapManager::SyncBlockReadMode] start, "
    // << "  - dmabuf_fd: " << block->dmabuf_fd
    // << "  - global_fd: " << block->global_fd
    // << "  - size: " << block->size
    // << "  - ptr: " << block->ptr
    // << std::endl;

    if (!is_work_.load()) {
        return false;
    }

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::SyncBlockReadMode] block is nullptr." << std::endl;
        return false;
    }

    if (block->dmabuf_fd < 0) {
        std::cout << "[DmaBufHeapManager::SyncBlockReadMode] dmabuf_fd < 0." << std::endl;
        return false;
    }

    if (block->ptr == MAP_FAILED) {
        std::cout << "[DmaBufHeapManager::SyncBlockReadMode] block->ptr == MAP_FAILED." << std::endl;
        return false;
    }

    dmabuf_heap_sync_cache_inval(reinterpret_cast<char*>(block->ptr),
                                 reinterpret_cast<char*>(block->ptr) + block->size);

    // std::cout << "[DmaBufHeapManager::SyncBlockReadMode] end, "
    // << "  - dmabuf_fd: " << block->dmabuf_fd
    // << "  - global_fd: " << block->global_fd
    // << "  - size: " << block->size
    // << "  - ptr: " << block->ptr
    // << std::endl;

    return true;
}

bool DmaBufHeapManager::SyncBlockWriteMode(Block* block)
{
    if (!is_work_.load()) {
        return false;
    }

    if (nullptr == block) {
        std::cout << "[DmaBufHeapManager::SyncBlockWriteMode] block is nullptr." << std::endl;
        return false;
    }

    if (block->dmabuf_fd < 0) {
        std::cout << "[DmaBufHeapManager::SyncBlockWriteMode] dmabuf_fd < 0." << std::endl;
        return false;
    }

    if (block->ptr == MAP_FAILED) {
        std::cout << "[DmaBufHeapManager::SyncBlockWriteMode] block->ptr == MAP_FAILED."
                  << std::endl;
        return false;
    }

    dmabuf_heap_sync_cache_clean(reinterpret_cast<char*>(block->ptr),
                                 reinterpret_cast<char*>(block->ptr) + block->size);

    return true;
}

}  // namespace dal_dmabufheap